﻿using OPCAutomation;
using OPCPlatformCore.Exceptions;
using OPCPlatformCore.Models.OPCServerModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace OPCPlatformCore.Managers
{
    public class OPCServerManager
    {
        //原本设计为一个单例模式,后续测试后考虑到因为异步读取多线程的问题,为了避免线程切换造成的性能损失,故重新设计为每次请求都获取一个新的实例
        private OPCServer oPCServer;
        private OPCGroup oPCGroup;//此对象用于保存注册的机台信息
        //此变量设计为私有的,因只用于对象的缓存,不能被外界访问,否则可能造成数据的错误,因为读取是多线程的,必须保证每个线程的变量之间不会影响,所以此处使用ThreadLocal来限制
        private OPCServerMachine currentMachine;
        //此处的设计只连接本地的OPCServer,因远程OPCServer需要配置OPCDA十分麻烦且复杂,因此远程OPC连接通过OPC UA的方式来实现
        public OPCServerManager(string connectionString)
        {
            InitManager(connectionString);
        }
        private void InitManager(string connectionString)
        {
            try
            {
                oPCServer = new OPCServerClass();//此处需要将OPCAutomation的应用的迁入互操作类型设置为false,否则无法通过编译
                oPCServer.Connect(connectionString);
            }
            catch (Exception)
            {
                throw;
            }
        }
        /// <summary>
        /// 此方法用于注册机台信息与OPC Server绑定,此方法不应该异步执行,否则可能出现错误
        /// 建议此方法只在程序加载后执行一次即可
        /// </summary>
        /// <param name="oPCServerMachine"></param>
        public void RegisterMachine(OPCServerMachine oPCServerMachine)
        {
            if (oPCGroup != null)
            {
                throw new MachineRegistedException(oPCServerMachine.MachineName);
            }
            try
            {
                currentMachine = oPCServerMachine;
                if (oPCServer.ServerState != (int)OPCServerState.OPCRunning)
                {
                    throw new OPCServerStateException("OPCServer运行异常,请联系管理员!");
                }
                OPCGroup group = oPCServer.OPCGroups.Add(oPCServerMachine.MachineName);
                group.IsSubscribed = true;//注意此属性必须设置为true,否则无法正常读写数据
                group.IsActive = true;
                group.AsyncReadComplete += Group_AsyncReadComplete;
                group.AsyncWriteComplete += Group_AsyncWriteComplete;

                //初始化OPCItems
                foreach (var item in oPCServerMachine.TagInfos)
                {
                    group.OPCItems.AddItem(item.Address, item.ClientHandle);
                }

                oPCGroup = group;
            }
            catch (Exception)
            {
                throw;
            }
        }

        private void Group_AsyncWriteComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array Errors)
        {
            currentMachine.SetWriteSuccess(true);
        }

        private void Group_AsyncReadComplete(int TransactionID, int NumItems, ref Array ClientHandles, ref Array ItemValues, ref Array Qualities, ref Array TimeStamps, ref Array Errors)
        {
            for (int i = 1; i <= ItemValues.Length; i++)
            {
                int index = Convert.ToInt32(ClientHandles.GetValue(i).ToString());
                object value = ItemValues.GetValue(i);
                currentMachine.TagInfos[index].SetValue(value == null ? null : ItemValues.GetValue(i).ToString());
            }
            currentMachine.SetReadSuccess(true);
        }

        public OPCGroup GetGroupByMachineName(string machineName)
        {
            if (oPCGroup == null)
            {
                throw new MachineNoRegistedException(machineName);
            }
            
            return oPCGroup;
        }
        public OPCServerMachine GetMachineByMachineName(string machineName)
        {
            if (oPCGroup == null)
            {
                throw new MachineNoRegistedException(machineName);
            }
            return currentMachine;
        }
        /// <summary>
        /// 此功能的存在仅是提供一个后备的可以用来关闭OPCServer连接的功能,正常情况下,请勿调用此方法
        /// </summary>
        public void CloseOPC()
        {
            try
            {
                //释放掉所有的非托管资源
                //不执行此操作可能造成内存溢出
                oPCServer.OPCGroups.RemoveAll();
                oPCGroup = null;
                oPCServer.Disconnect();
                oPCServer = null;
            }
            catch (Exception)
            {
                throw;
            }
        }
    }
}
